import yaml
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from utils.file_utils import FileUtils
from selenium.webdriver.remote.webdriver import WebDriver

from utils.generate_logs_util import get_log


class Base():
    """
    Selenium自动化测试基础类，封装常用的页面操作方法。
    """

    def __init__(self, driver, yaml_path, page_node=None):
        """
        初始化基类

        参数:
            driver: 浏览器驱动实例
            yaml_path: yaml配置文件路径
            page_node: 初始页面节点名称（可选）
        """
        self.driver = driver
        self.yaml_path = yaml_path
        self.logger = get_log(__name__)

        # 加载YAML中所有节点的定位信息
        self.all_locators = self.load_all_locators()

        # 初始化当前节点（如果指定）
        self.current_node = None
        if page_node:
            self.switch_node(page_node)  # 调用切换方法初始化节点

    def load_all_locators(self):
        """加载YAML文件中所有节点的定位信息"""
        try:
            with open(self.yaml_path, 'r', encoding='utf-8') as f:
                locators = yaml.safe_load(f) or {}  # 处理空文件
            self.logger.info(f"成功加载YAML配置：{self.yaml_path}")
            return locators
        except Exception as e:
            self.logger.error(f"加载YAML配置失败：{str(e)}")
            raise  # 抛出异常，避免后续操作出错

    def switch_node(self, node_name):
        """
        切换当前操作的YAML节点

        参数:
            node_name: 要切换的节点名称（需在YAML中存在）
        """
        if node_name not in self.all_locators:
            error_msg = f"YAML中不存在节点：{node_name}（可用节点：{list(self.all_locators.keys())}）"
            self.logger.error(error_msg)
            raise KeyError(error_msg)

        self.current_node = node_name
        self.locators = self.all_locators[node_name]  # 当前节点的定位信息
        self.logger.info(f"已切换到节点：{node_name}")

    def open_url(self,url: str):
        """
        打开指定URL
        :param url: 网址
        """
        self.driver.get(url)

    def find_element(self,loc_key):
        """
        根据当前节点的定位信息查找元素

        参数:
            locator_key: 元素在当前节点下的键名（如"username_input"）
        """
        # 校验当前节点是否已设置
        if not self.current_node:
            error_msg = "未指定当前节点，请先调用switch_node()切换节点"
            self.logger.error(error_msg)
            raise ValueError(error_msg)

        # 获取元素定位信息
        locator_info = self.locators.get(loc_key)
        if not locator_info:
            error_msg = f"节点{self.current_node}中不存在元素：{loc_key}"
            self.logger.error(error_msg)
            raise KeyError(error_msg)

        # 解析定位方式和定位值
        by = getattr(By, locator_info["by"].upper())  # 兼容小写（如"xpath"自动转为By.XPATH）
        value = locator_info["value"]
        timeout = locator_info.get("timeout", 5)  # 默认为5秒
        loc=(by,value)
        try:
            WebDriverWait(self.driver,20,0.5).until(EC.visibility_of_element_located((by,value)))
            return self.driver.find_element(*loc)
        except Exception as e:
            print(e)

    def click(self,loc):
        """
        点击指定元素
        :param loc: 元素定位元组
        """
        self.find_element(loc).click()

    def send_keys(self,loc,info):
        """
        向指定元素输入内容
        :param loc: 元素定位元组
        :param info: 输入内容
        """
        self.find_element(loc).send_keys(info)

    def clear(self,loc):
        """
        清空输入框内容
        :param loc: 元素定位元组
        """
        self.find_element(loc).clear()

    def text(self,loc):
        """
        获取元素文本内容
        :param loc: 元素定位元组
        :return: 文本内容
        """
        return self.find_element(loc).text

    # 特殊技术处理
    # 多窗口切换
    def new_handles(self):
        """
        切换到最新打开的窗口
        """
        self.driver.switch_to.window(self.driver.window_handles[-1])

    # 进入frame框架
    def frame(self,loc):
        """
        进入指定frame框架
        :param loc: frame定位元组
        """
        self.driver.switch_to.frame(self.find_element(loc))

    # 退出frame框架
    def frame_uncon(self):
        """
        退出frame，回到默认页面
        """
        self.driver.switch_to.default_content()

    # 滚动到任意像素
    def scrollto_x(self,x):
        """
        页面滚动到指定像素位置
        :param x: 像素值
        """
        self.driver.execute_script(f'window.scrollTo(0,{x})')

    # 滚动到底部
    def scrollto_height(self):
        """
        页面滚动到底部
        """
        self.driver.execute_script('window.scrollTo(0,document.body.scrollHeight)')

    # 弹窗 accept
    def accept(self):
        """
        接受弹窗
        """
        alert = self.driver.switch_to.alert
        alert.accept()

    # 弹窗 dismiss
    def dismiss(self):
        """
        取消弹窗
        """
        alert = self.driver.switch_to.alert
        alert.dismiss()

    # 弹窗 send_keys accept
    def send_accept(self,info):
        """
        向弹窗输入内容并接受
        :param info: 输入内容
        """
        prompt = self.driver.switch_to.alert
        prompt.send_keys(info)
        prompt.accept()

    # 弹窗 send_keys dismiss
    def send_dismiss(self, info):
        """
        向弹窗输入内容并取消
        :param info: 输入内容
        """
        prompt = self.driver.switch_to.alert
        prompt.send_keys(info)
        prompt.dismiss()

    # 按下表
    def select_index(self,lox,x):
        """
        下拉框按索引选择
        :param lox: 下拉框定位元组
        :param x: 索引值
        """
        index = Select(self.find_element(lox))
        index.select_by_index(x)

    # 按value
    def select_value(self,loc,info):
        """
        下拉框按value选择
        :param loc: 下拉框定位元组
        :param info: value值
        """
        value = Select(self.find_element(loc))
        value.select_by_value(info)

    # 按text
    def select_text(self,loc,tex):
        """
        下拉框按文本选择
        :param loc: 下拉框定位元组
        :param tex: 显示文本
        """
        text = Select(self.find_element(loc))
        text.select_by_visible_text(tex)

    # 鼠标双击
    def a_double(self,loc):
        """
        鼠标双击指定元素
        :param loc: 元素定位元组
        """
        ActionChains(self.driver).double_click(self.find_element(loc)).perform()

    # 鼠标右击
    def a_contex(self,loc):
        """
        鼠标右击指定元素
        :param loc: 元素定位元组
        """
        ActionChains(self.driver).context_click(self.find_element(loc)).perform()

    # 鼠标悬停
    def a_move(self,loc):
        """
        鼠标悬停到指定元素
        :param loc: 元素定位元组
        """
        ActionChains(self.driver).move_to_element(self.find_element(loc)).perform()

    # 鼠标拖拽
    def a_tuo(self,x,y):
        """
        鼠标拖拽元素到目标位置
        :param x: 源元素定位元组
        :param y: 目标元素定位元组
        """
        ActionChains(self.driver).drag_and_drop(self.find_element(x),self.find_element(y)).perform()

    # 鼠标三击
    def a_three(self,loc):
        """
        鼠标三击指定元素
        :param loc: 元素定位元组
        """
        ActionChains(self.driver).double_click(self.find_element(loc)).click(self.find_element(loc)).perform()

    # 鼠标释放
    def a_shi(self,loc):
        """
        鼠标释放指定元素
        :param loc: 元素定位元组
        """
        ActionChains(self.driver).release(self.find_element(loc)).perform()

    # 鼠标按住不动
    def a_hold(self,loc):
        """
        鼠标按住指定元素不释放
        :param loc: 元素定位元组
        """
        ActionChains(self.driver).click_and_hold(self.find_element(loc)).perform()

    # 键盘事件
    def keys_enter(self,loc):
        """
        向元素发送回车键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.ENTER)

    def keys_ctrl_c(self,loc):
        """
        向元素发送Ctrl+C组合键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.CONTROL,'c')

    def keys_ctrl_a(self,loc):
        """
        向元素发送Ctrl+A组合键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.CONTROL,'a')

    def keys_ctrl_x(self,loc):
        """
        向元素发送Ctrl+X组合键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.CONTROL,'x')

    def keys_ctrl_v(self,loc):
        """
        向元素发送Ctrl+V组合键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.CONTROL,'v')

    def keys_tab(self,loc):
        """
        向元素发送Tab键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.TAB)

    def keys_back(self,loc):
        """
        向元素发送退格键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.BACKSPACE)

    def keys_del(self,loc):
        """
        向元素发送Delete键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.DELETE)

    def keys_shift(self,loc):
        """
        向元素发送Shift键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.SHIFT)

    def keys_left(self,loc):
        """
        向元素发送左方向键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.LEFT)

    def keys_right(self,loc):
        """
        向元素发送右方向键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.RIGHT)

    def keys_top(self,loc):
        """
        向元素发送上方向键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.UP)

    def keys_bot(self,loc):
        """
        向元素发送下方向键
        :param loc: 元素定位元组
        """
        self.find_element(loc).send_keys(Keys.DOWN)